.TH std::forward_like 3 "2024.06.10" "http://cppreference.com" "C++ Standard Libary"
.SH NAME
std::forward_like \- std::forward_like

.SH Synopsis
   Defined in header <utility>
   template< class T, class U >                                    (since C++23)
   [[nodiscard]] constexpr auto&& forward_like( U&& x ) noexcept;

   Returns a reference to x which has similar properties to T&&.

   The return type is determined as below:

    1. If std::remove_reference_t<T> is a const-qualified type, then the referenced
       type of the return type is const std::remove_reference_t<U>. Otherwise, the
       referenced type is std::remove_reference_t<U>.
    2. If T&& is an lvalue reference type, then the return type is also an lvalue
       reference type. Otherwise, the return type is an rvalue reference type.

   If T is not a referenceable type, the program is ill-formed.

.SH Parameters

   x - a value needs to be forwarded like type T

.SH Return value

   A reference to x of the type determined as above.

.SH Notes

   Like std::forward, std::move, and std::as_const, std::forward_like is a type cast
   that only influences the value category of an expression, or potentially adds
   const-qualification.

   When m is an actual member and thus o.m a valid expression, this is usually spelled
   as std::forward<decltype(o)>(o).m in C++20 code.

   This leads to three possible models, called merge, tuple, and language.

     * merge: merge the const qualifiers, and adopt the value category of the Owner.
     * tuple: what std::get<0>(Owner) does, assuming Owner is a std::tuple<Member>.
     * language: what std::forward<decltype(Owner)>(o).m does.

   The main scenario that std::forward_like caters to is adapting “far” objects.
   Neither the tuple nor the language scenarios do the right thing for that main
   use-case, so the merge model is used for std::forward_like.

     Feature-test macro    Value    Std        Feature
   __cpp_lib_forward_like 202207L (C++23) std::forward_like

.SH Possible implementation

   template<class T, class U>
   [[nodiscard]] constexpr auto&& forward_like(U&& x) noexcept
   {
       constexpr bool is_adding_const = std::is_const_v<std::remove_reference_t<T>>;
       if constexpr (std::is_lvalue_reference_v<T&&>)
       {
           if constexpr (is_adding_const)
               return std::as_const(x);
           else
               return static_cast<U&>(x);
       }
       else
       {
           if constexpr (is_adding_const)
               return std::move(std::as_const(x));
           else
               return std::move(x);
       }
   }

.SH Example


// Run this code

 #include <cstddef>
 #include <iostream>
 #include <memory>
 #include <optional>
 #include <type_traits>
 #include <utility>
 #include <vector>

 struct TypeTeller
 {
     void operator()(this auto&& self)
     {
         using SelfType = decltype(self);
         using UnrefSelfType = std::remove_reference_t<SelfType>;
         if constexpr (std::is_lvalue_reference_v<SelfType>)
         {
             if constexpr (std::is_const_v<UnrefSelfType>)
                 std::cout << "const lvalue\\n";
             else
                 std::cout << "mutable lvalue\\n";
         }
         else
         {
             if constexpr (std::is_const_v<UnrefSelfType>)
                 std::cout << "const rvalue\\n";
             else
                 std::cout << "mutable rvalue\\n";
         }
     }
 };

 struct FarStates
 {
     std::unique_ptr<TypeTeller> ptr;
     std::optional<TypeTeller> opt;
     std::vector<TypeTeller> container;

     auto&& from_opt(this auto&& self)
     {
         return std::forward_like<decltype(self)>(self.opt.value());
         // It is OK to use std::forward<decltype(self)>(self).opt.value(),
         // because std::optional provides suitable accessors.
     }

     auto&& operator[](this auto&& self, std::size_t i)
     {
         return std::forward_like<decltype(self)>(self.container.at(i));
         // It is not so good to use std::forward<decltype(self)>(self)[i], because
         // containers do not provide rvalue subscript access, although they could.
     }

     auto&& from_ptr(this auto&& self)
     {
         if (!self.ptr)
             throw std::bad_optional_access{};
         return std::forward_like<decltype(self)>(*self.ptr);
         // It is not good to use *std::forward<decltype(self)>(self).ptr, because
         // std::unique_ptr<TypeTeller> always dereferences to a non-const lvalue.
     }
 };

 int main()
 {
     FarStates my_state
     {
         .ptr{std::make_unique<TypeTeller>()},
         .opt{std::in_place, TypeTeller{}},
         .container{std::vector<TypeTeller>(1)},
     };

     my_state.from_ptr()();
     my_state.from_opt()();
     my_state[0]();

     std::cout << '\\n';

     std::as_const(my_state).from_ptr()();
     std::as_const(my_state).from_opt()();
     std::as_const(my_state)[0]();

     std::cout << '\\n';

     std::move(my_state).from_ptr()();
     std::move(my_state).from_opt()();
     std::move(my_state)[0]();

     std::cout << '\\n';

     std::move(std::as_const(my_state)).from_ptr()();
     std::move(std::as_const(my_state)).from_opt()();
     std::move(std::as_const(my_state))[0]();

     std::cout << '\\n';
 }

.SH Output:

 mutable lvalue
 mutable lvalue
 mutable lvalue

 const lvalue
 const lvalue
 const lvalue

 mutable rvalue
 mutable rvalue
 mutable rvalue

 const rvalue
 const rvalue
 const rvalue

.SH See also

   move     obtains an rvalue reference
   \fI(C++11)\fP  \fI(function template)\fP
   forward  forwards a function argument
   \fI(C++11)\fP  \fI(function template)\fP
   as_const obtains a reference to const to its argument
   \fI(C++17)\fP  \fI(function template)\fP
